A JSON Schema for TimelineJS3ΒΆ

Binder

JSON Schema is a good baseline for communicating between different computer languages, and is basically understandable by humans.

[1]:
import jsonschema, json, pathlib, pprint, IPython
[2]:
timeline_schema = {
    "$schema": "http://json-schema.org/draft-07/schema#",
    "$id": "https://timeline.knightlab.com/docs/json-format.html",
    "type": "object",
    "anyOf": [
        {"$ref": "#/definitions/timeline"}
    ],
    "definitions": {}
}
[3]:
timeline_schema["definitions"]["timeline"] = {
    "type": "object",
    "required": ["events"],
    "additionalProperties": False,
    "properties": {
        "events": {
            "description": "A JSON list of \"slide\" objects (see below)",
            "type": "array",
            "items": {"$ref": "#/definitions/event"}
        },
        "title": {
            "description": "A \"slide\" object (see below)",
            "$ref": "#/definitions/title-event",
            "type": "object"
        },
        "eras": {
            "description": "A JSON list of \"era\" objects (see below)",
            "items": {"$ref": "#/definitions/era"},
            "type": "array"
        },
        "scale": {
            "type": "string",
            "enum": ["human", "cosmological"],
            "description": "If no scale is specified, the default is human. The cosmological scale is required to handle dates in the very distant past or future. (Before Tuesday, April 20th, 271,821 BCE after Saturday, September 13 275,760 CE) For the cosmological scale, only the year is considered, but it's OK to have a cosmological timeline with years between 271,821 BCE and 275,760 CE."
        }
    }
}
[4]:
timeline_schema["definitions"]["event"] = {
    "type": "object",
    "anyOf": [
        {"$ref": "#/definitions/title-event"},
    ],
    "required": ["start_date"]
}

[5]:
timeline_schema["definitions"]["title-event"] = {
    "type": "object",
    "additionalProperties": False,
    "properties": {
        "start_date": {"$ref": "#/definitions/date"},
        "end_date": {"$ref": "#/definitions/date"},
        "text": {"$ref": "#/definitions/text"},
        "media": {"$ref": "#/definitions/media"},
        "group": {
            "type": "string",
            "description": "Any text. If present, Timeline will organize events with the same value for group to be in the same row or adjacent rows, separate from events in other groups. The common value for the group will be shown as a label at the left edge of the navigation."
        },
        "display_date": {
            "type": "string",
            "description": "A string which will be used when Timeline displays the date for this. If used, override's display_date values set on the start or end date for this event, which is useful if you want to control how the two dates relate to each other."
        },
        "background": {
            "$ref": "#/definitions/background"
        }
    }
}

[6]:
timeline_schema["definitions"]["background"] = {
    "additionalProperties": False,
    "type": "object",
    "properties": {
        "url": {"type": "string", "format": "uri", "description": "the fully-qualified URL pointing to an image which will be used as the background"},
        "color": {"type": "string", "description": "a CSS color, in hexadecimal (e.g. #0f9bd1) or a valid CSS color keyword."},
        "autolink": {"type": "boolean", "default": True,
                     "description": "A boolean value (true or false). Defaults to true, which means that Timeline will scan text fields and automatically add `<a>` tags so that links and email addresses are \"clickable.\" If set to `false`, you may still manually apply the tags in the appropriate fields when you want links. Autolinking applies to the text field in a text object and the caption and credit fields in a media object."},
        "unique_id": {"type": "string", "description": "A string value which is unique among all slides in your timeline. If not specified, TimelineJS will construct an ID based on the headline, but if you later edit your headline, the ID will change. Unique IDs are used when the `hash_bookmark` option is used, and can also be used with the `timeline.goToId()` method to programmatically move the timeline to a specific slide."}
    }
}
[7]:
timeline_schema["definitions"]["era"] = {
    "additionalProperties": False,
    "type": "object",
    "description": "Era objects are JSON objects which are used to label a span of time on the timeline navigation component. In structure, they are essentially very restricted \"slide\" objects.",
    "required": ["start_date", "end_date"],
    "properties": {
        "start_date": {"$ref": "#/definitions/date"},
        "end_date": {"$ref": "#/definitions/date"},
        "text": {"$ref": "#/definitions/text"}
    }
}
[8]:
timeline_schema["definitions"]["date"] = {
    "additionalProperties": False,
    "type": "object",
    "required": ["year"],
    "properties": {
        "year": {"type": "string", "pattern": "-?\d+"}, #{"type": "number"},
        "month": {"type": "string", "pattern": "[1-9]|1[0-2]"}, #{"type": "number", "minimum": 1, "maximum": 12},
        "day": {"type": "string", "pattern": "\d+"}, # {"type": "number"},
        "hour": {"type": "string", "pattern": "1?[0-9]|2[0-3]"}, #{"type": "number", "minimum": 0, "maximum": 23},
        "minute": {"type": "string", "pattern": "[1-5]?[0-9]"}, #{"type": "number", "minimum": 0, "maximum": 59},
        "second": {"type": "string", "pattern": "[1-5]?[0-9]"}, #{"type": "number", "minimum": 0, "maximum": 59},
        "millisecond": {"type": "string", "pattern": "\d+"}, # {"type": "number"},
        "display_date": {"type": "string", "description": "A string for presenting the date. Useful if Timeline's date formatting doesn't fit your needs."}
    }
}
[9]:
timeline_schema["definitions"]["text"] = {
    "additionalProperties": False,
    "type": "object",
    "properties": {
        "headline": {"type": "string", "description": "Any text. HTML markup is OK. Blank is also OK."},
        "text": {"type": "string", "description": "Any text. HTML markup is OK. Blank is OK. Not used for `era` objects."}
    }
}
[10]:
timeline_schema["definitions"]["media"] = {
    "additionalProperties": False,
    "required": ["url"],
    "type": "object",
    "properties": {
        "url": {"type": "string", "description": "In most cases, a URL (see media type documentation for complete details)."},
        "caption": {"type": "string", "description": "Any text. HTML markup is OK."},
        "credit": {"type": "string", "description": "Any text. HTML markup is OK."},
        "thumbnail": {"type": "string", "description": "A URL for an image to use in the timenav marker for this event. If omitted, Timeline will use an icon based on the type of media. Not relevant for title slides, because they do not have a marker."},
        "alt": {"type": "string", "description": "An `alt` tag for your image. If none is provided, the `caption`, if any, will be used."},
        "title": {"type": "string", "description": "An title for your image. If none is provided, the `caption`, if any, will be used."},
        "link": {"type": "string", "description": "Optional URL to use as the href for wrapping the media with an <a> tag."},
        "link_target": {"type": "string", "description": "Optional target to be associated with link if used."},
    }
}

Actually validate.

[11]:
validator = jsonschema.validators.Draft7Validator(timeline_schema)

Clone the GitHub repository with:

[12]:
repo = pathlib.Path("vendor") / "TimelineJS3"
[13]:
if not repo.exists():
    !mkdir vendor && cd vendor && git clone https://github.com/NUKnightLab/TimelineJS3.git
[14]:
for example_file in repo.rglob("timeline3.json"):
    print(example_file)
    example_json = json.loads(example_file.read_text())
    errors = []
    try:
        errors = list(validator.iter_errors(example_json))
    except Exception as err:
        print(err)
    print(errors or "\tno errors")
vendor/TimelineJS3/website/templates/examples/timeline3.json
        no errors
vendor/TimelineJS3/website/templates/examples/shit-people-say/timeline3.json
        no errors
vendor/TimelineJS3/website/templates/examples/houston/timeline3.json
        no errors
vendor/TimelineJS3/website/templates/examples/soundcite/timeline3.json
        no errors
vendor/TimelineJS3/website/templates/examples/user-interface/timeline3.json
        no errors
vendor/TimelineJS3/website/templates/examples/republican/timeline3.json
        no errors
vendor/TimelineJS3/website/templates/examples/mediatypes/timeline3.json
        no errors

the full schema

[15]:
IPython.display.Markdown(f"""```json
{json.dumps(timeline_schema, indent=2, sort_keys=True)}
```""")
[15]:
{
  "$id": "https://timeline.knightlab.com/docs/json-format.html",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "anyOf": [
    {
      "$ref": "#/definitions/timeline"
    }
  ],
  "definitions": {
    "background": {
      "additionalProperties": false,
      "properties": {
        "autolink": {
          "default": true,
          "description": "A boolean value (true or false). Defaults to true, which means that Timeline will scan text fields and automatically add `<a>` tags so that links and email addresses are \"clickable.\" If set to `false`, you may still manually apply the tags in the appropriate fields when you want links. Autolinking applies to the text field in a text object and the caption and credit fields in a media object.",
          "type": "boolean"
        },
        "color": {
          "description": "a CSS color, in hexadecimal (e.g. #0f9bd1) or a valid CSS color keyword.",
          "type": "string"
        },
        "unique_id": {
          "description": "A string value which is unique among all slides in your timeline. If not specified, TimelineJS will construct an ID based on the headline, but if you later edit your headline, the ID will change. Unique IDs are used when the `hash_bookmark` option is used, and can also be used with the `timeline.goToId()` method to programmatically move the timeline to a specific slide.",
          "type": "string"
        },
        "url": {
          "description": "the fully-qualified URL pointing to an image which will be used as the background",
          "format": "uri",
          "type": "string"
        }
      },
      "type": "object"
    },
    "date": {
      "additionalProperties": false,
      "properties": {
        "day": {
          "pattern": "\\d+",
          "type": "string"
        },
        "display_date": {
          "description": "A string for presenting the date. Useful if Timeline's date formatting doesn't fit your needs.",
          "type": "string"
        },
        "hour": {
          "pattern": "1?[0-9]|2[0-3]",
          "type": "string"
        },
        "millisecond": {
          "pattern": "\\d+",
          "type": "string"
        },
        "minute": {
          "pattern": "[1-5]?[0-9]",
          "type": "string"
        },
        "month": {
          "pattern": "[1-9]|1[0-2]",
          "type": "string"
        },
        "second": {
          "pattern": "[1-5]?[0-9]",
          "type": "string"
        },
        "year": {
          "pattern": "-?\\d+",
          "type": "string"
        }
      },
      "required": [
        "year"
      ],
      "type": "object"
    },
    "era": {
      "additionalProperties": false,
      "description": "Era objects are JSON objects which are used to label a span of time on the timeline navigation component. In structure, they are essentially very restricted \"slide\" objects.",
      "properties": {
        "end_date": {
          "$ref": "#/definitions/date"
        },
        "start_date": {
          "$ref": "#/definitions/date"
        },
        "text": {
          "$ref": "#/definitions/text"
        }
      },
      "required": [
        "start_date",
        "end_date"
      ],
      "type": "object"
    },
    "event": {
      "anyOf": [
        {
          "$ref": "#/definitions/title-event"
        }
      ],
      "required": [
        "start_date"
      ],
      "type": "object"
    },
    "media": {
      "additionalProperties": false,
      "properties": {
        "alt": {
          "description": "An `alt` tag for your image. If none is provided, the `caption`, if any, will be used.",
          "type": "string"
        },
        "caption": {
          "description": "Any text. HTML markup is OK.",
          "type": "string"
        },
        "credit": {
          "description": "Any text. HTML markup is OK.",
          "type": "string"
        },
        "link": {
          "description": "Optional URL to use as the href for wrapping the media with an <a> tag.",
          "type": "string"
        },
        "link_target": {
          "description": "Optional target to be associated with link if used.",
          "type": "string"
        },
        "thumbnail": {
          "description": "A URL for an image to use in the timenav marker for this event. If omitted, Timeline will use an icon based on the type of media. Not relevant for title slides, because they do not have a marker.",
          "type": "string"
        },
        "title": {
          "description": "An title for your image. If none is provided, the `caption`, if any, will be used.",
          "type": "string"
        },
        "url": {
          "description": "In most cases, a URL (see media type documentation for complete details).",
          "type": "string"
        }
      },
      "required": [
        "url"
      ],
      "type": "object"
    },
    "text": {
      "additionalProperties": false,
      "properties": {
        "headline": {
          "description": "Any text. HTML markup is OK. Blank is also OK.",
          "type": "string"
        },
        "text": {
          "description": "Any text. HTML markup is OK. Blank is OK. Not used for `era` objects.",
          "type": "string"
        }
      },
      "type": "object"
    },
    "timeline": {
      "additionalProperties": false,
      "properties": {
        "eras": {
          "description": "A JSON list of \"era\" objects (see below)",
          "items": {
            "$ref": "#/definitions/era"
          },
          "type": "array"
        },
        "events": {
          "description": "A JSON list of \"slide\" objects (see below)",
          "items": {
            "$ref": "#/definitions/event"
          },
          "type": "array"
        },
        "scale": {
          "description": "If no scale is specified, the default is human. The cosmological scale is required to handle dates in the very distant past or future. (Before Tuesday, April 20th, 271,821 BCE after Saturday, September 13 275,760 CE) For the cosmological scale, only the year is considered, but it's OK to have a cosmological timeline with years between 271,821 BCE and 275,760 CE.",
          "enum": [
            "human",
            "cosmological"
          ],
          "type": "string"
        },
        "title": {
          "$ref": "#/definitions/title-event",
          "description": "A \"slide\" object (see below)",
          "type": "object"
        }
      },
      "required": [
        "events"
      ],
      "type": "object"
    },
    "title-event": {
      "additionalProperties": false,
      "properties": {
        "background": {
          "$ref": "#/definitions/background"
        },
        "display_date": {
          "description": "A string which will be used when Timeline displays the date for this. If used, override's display_date values set on the start or end date for this event, which is useful if you want to control how the two dates relate to each other.",
          "type": "string"
        },
        "end_date": {
          "$ref": "#/definitions/date"
        },
        "group": {
          "description": "Any text. If present, Timeline will organize events with the same value for group to be in the same row or adjacent rows, separate from events in other groups. The common value for the group will be shown as a label at the left edge of the navigation.",
          "type": "string"
        },
        "media": {
          "$ref": "#/definitions/media"
        },
        "start_date": {
          "$ref": "#/definitions/date"
        },
        "text": {
          "$ref": "#/definitions/text"
        }
      },
      "type": "object"
    }
  },
  "type": "object"
}
[ ]: